# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools

from coin.base.param_util import to_list
from coin.base.datetime_util import to_datetime
from coin.exchange.okex_futures.kr_rest.futures_product import (OkexFuturesProduct)
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.strategy.mm.simple_sim import result_util
from coin.strategy.mm.simple_sim.strategy.pass_unhedge_3 import (PassUnhedgedSimStrategy)
from coin.strategy.mm.simple_sim.profile.pass_unhedge_xbtusd_3 import (linear_sell_edge,
                                                                       linear_buy_edge)


def get_products():
  return [
      BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL'),
      OkexFuturesProduct.FromStr('BTC-USD.QUARTER')
  ]


def get_machines():
  return ['feed-1.aliyun-cn-hongkong']


def get_time_ranges():
  ranges = []
  cur_dt = datetime.datetime(2018, 5, 10, 0, 0, 0)
  end_dt = datetime.datetime(2018, 5, 15, 0, 0, 0)
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def get_lot_size(timestamp):
  dt = to_datetime(timestamp)
  if 0 <= dt.hour and dt.hour <= 15:
    return 1.
  else:
    return 4.


def get_min_pos(timestamp):
  return -get_lot_size(timestamp) * 5


def get_max_pos(timestamp):
  return get_lot_size(timestamp) * 5


def get_strategy(from_ts, to_ts):
  products = get_products()
  pass_product = products[1]
  pass_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(200.)[1][-1][0]),
                                  (lambda book: book.get_notional_bids_by_qty(200.)[1][-1][0]))

  ref_product = products[0]
  ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(20000.)[1][-1][0]),
                                 (lambda book: book.get_notional_bids_by_qty(20000.)[1][-1][0]))

  NS_PER_SECOND = (10**9)
  NS_PER_MINUTE = 60 * NS_PER_SECOND

  strategy_list = []

  for basis_ma_window in [1, 2, 3, 5, 8]:
    for edge_bp in [1, 2, 3, 5, 7, 9]:
      for agg_edge in [None, 13. / 10000.]:
        for close_edge_bp in [-1, edge_bp]:
          stack = 5
          lot_size = 0.1

          pricing_param = {
              'basis_ma_window': basis_ma_window * NS_PER_MINUTE,
              'tick': 0.01,
              'book_askt_func_1': ref_product_true_book_funcs[0],
              'book_bidt_func_1': ref_product_true_book_funcs[1],
              'book_askt_func_2': pass_product_true_book_funcs[0],
              'book_bidt_func_2': pass_product_true_book_funcs[1]
          }

          executor_param = {
              'lot_size': lot_size,
              'min_pos': -lot_size * stack,
              'max_pos': lot_size * stack,
              'maker_fee': 0. / 10000.,
              'taker_fee': 2. / 10000.,
              'execution_delay': 1 * NS_PER_SECOND,
              'post_only': False,
              'trade_qty_func': (lambda trade: 100 * trade.qty / trade.price)
          }

          use_agg = 'without_agg'
          if agg_edge:
            use_agg = 'with_agg'
          name = '%02dm.%02dbp.%02dbp.%02dstack.%s.%s' % (basis_ma_window,
                                                          edge_bp,
                                                          close_edge_bp,
                                                          stack,
                                                          use_agg,
                                                          to_datetime(from_ts).strftime('%Y%m%d'))

          strategy = PassUnhedgedSimStrategy(pass_product,
                                             ref_product,
                                             functools.partial(linear_sell_edge,
                                                               edge_bp / 10000.,
                                                               close_edge_bp / 10000.,
                                                               1.),
                                             functools.partial(linear_buy_edge,
                                                               edge_bp / 10000.,
                                                               close_edge_bp / 10000.,
                                                               1.),
                                             pricing_param,
                                             executor_param,
                                             trade_after=basis_ma_window,
                                             name=name,
                                             agg_edge=agg_edge,
                                             feature_filepath=('out/feature.%s.csv' % name),
                                             fill_filepath=('out/fill.%s.csv' % name))
          strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  marks = []

  last_pnl = 0
  qty_sum = 0
  next_hour = 0
  for fill in strategy.fills:
    ts = to_datetime(fill.time)
    while ts.hour >= next_hour:
      marks.append({'hour': next_hour, 'pnl': last_pnl, 'qty_sum': qty_sum})
      next_hour += 4
    last_pnl = fill.balance
    qty_sum += fill.qty

  while next_hour <= 24:
    marks.append({'hour': next_hour, 'pnl': last_pnl, 'qty_sum': qty_sum})
    next_hour += 4

  result = []
  print(marks)
  for i in range(1, len(marks)):
    range_pnl = marks[i]['pnl'] - marks[i - 1]['pnl']
    range_qty_sum = marks[i]['qty_sum'] - marks[i - 1]['qty_sum']
    result.append({
        'name': ('%s-%02d-%02d' % (strategy.name, 4 * (i - 1), 4 * i)),
        'pnl': range_pnl,
        'qty_sum': range_qty_sum
    })

  return result


def aggregate_result(results):
  concatenated = []
  for r in results:
    concatenated += to_list(r)

  return result_util.aggregate_sim_result(concatenated,
                                          aggregate_key_func=(lambda r:
                                                              (r['name'][:-15], r['name'][-5:])))
